/*
 * Copyright (c) 2011-2013, Peter Abeles. All Rights Reserved.
 *
 * This file is part of BoofCV (http://boofcv.org).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package boofcv.alg.filter.convolve.down;

import boofcv.struct.convolve.Kernel1D_F32;
import boofcv.struct.convolve.Kernel1D_I32;
import boofcv.struct.convolve.Kernel2D_F32;
import boofcv.struct.convolve.Kernel2D_I32;
import boofcv.struct.image.*;

/**
 * <p>
 * Convolves a 1D kernel in the horizontal or vertical direction while skipping pixels across an image's border.  The
 * kernel is re-normalizing the depending upon the amount of overlap it has with the image.  These functions will
 * NOT work on kernels which are large than the image.
 * </p>
 *
 * <p>
 * NOTE: Do not modify.  Automatically generated by {@link GenerateConvolveDownNormalized_JustBorder}.
 * </p>
 *
 * @author Peter Abeles
 */
@SuppressWarnings({"ForLoopReplaceableByForEach"})
public class ConvolveDownNormalized_JustBorder {

	public static void horizontal(Kernel1D_F32 kernel, ImageFloat32 input, ImageFloat32 output , int skip ) {
		final float[] dataSrc = input.data;
		final float[] dataDst = output.data;
		final float[] dataKer = kernel.data;

		final int radius = kernel.getRadius();
		final int offset = UtilDownConvolve.computeOffset(skip,radius);
		final int offsetEnd = UtilDownConvolve.computeMaxSide(input.width,skip,radius)+skip;

		final int width = input.width - input.width % skip;
		final int height = input.getHeight();

		for (int y = 0; y < height; y++) {

			int indexDest = output.startIndex + y*output.stride;

			for( int x = 0; x < offset; x += skip ) {
				int indexSrc = input.startIndex + y*input.stride+x;
				float total = 0;
				float weight = 0;

				for( int k = -x; k <= radius; k++ ) {
					float w = dataKer[k+radius];
					weight += w;
					total += (dataSrc[indexSrc+k]) * w;
				}
				dataDst[indexDest++] = (total/weight);
			}

			indexDest = output.startIndex + y*output.stride + offsetEnd/skip;

			for( int x = offsetEnd; x < width; x += skip ) {
				int indexSrc = input.startIndex + y*input.stride+x;
				float total = 0;
				float weight = 0;

				int endKernel = input.width-x-1;
				if( endKernel > radius ) endKernel = radius;

				for( int k = -radius; k <= endKernel; k++ ) {
					float w = dataKer[k+radius];
					weight += w;
					total += (dataSrc[indexSrc+k]) * w;
				}
				dataDst[indexDest++] = (total/weight);
			}
		}
	}

	public static void vertical(Kernel1D_F32 kernel, ImageFloat32 input, ImageFloat32 output , int skip ) {
		final float[] dataSrc = input.data;
		final float[] dataDst = output.data;
		final float[] dataKer = kernel.data;

		final int radius = kernel.getRadius();
		final int offset = UtilDownConvolve.computeOffset(skip,radius);
		final int offsetEnd = UtilDownConvolve.computeMaxSide(input.height,skip,radius)+skip;

		final int width = input.width;
		final int height = input.height - input.height % skip;

		for( int y = 0; y < offset; y += skip ) {
			int indexDest = output.startIndex + (y/skip)*output.stride;

			for( int x = 0; x < width; x++ ) {
				int indexSrc = input.startIndex + y*input.stride+x;
				float total = 0;
				float weight = 0;

				for( int k = -y; k <= radius; k++ ) {
					float w = dataKer[k+radius];
					weight += w;
					total += (dataSrc[indexSrc+k*input.stride]) * w;
				}
				dataDst[indexDest++] = (total/weight);
			}
		}

		for( int y = offsetEnd; y < height; y += skip ) {
			int indexDest = output.startIndex + (y/skip)*output.stride;
			int endKernel = input.height-y-1;
			if( endKernel > radius ) endKernel = radius;

			for( int x = 0; x < width; x++ ) {
				int indexSrc = input.startIndex + y*input.stride+x;
				float total = 0;
				float weight = 0;

				for( int k = -radius; k <= endKernel; k++ ) {
					float w = dataKer[k+radius];
					weight += w;
					total += (dataSrc[indexSrc+k*input.stride]) * w;
				}
				dataDst[indexDest++] = (total/weight);
			}
		}
	}

	public static void convolve(Kernel2D_F32 kernel, ImageFloat32 input, ImageFloat32 output , int skip ) {
		final float[] dataSrc = input.data;
		final float[] dataDst = output.data;
		final float[] dataKer = kernel.data;

		final int radius = kernel.getRadius();
		final int kernelWidth = kernel.getWidth();

		final int width = input.width - input.width % skip;
		final int height = input.height - input.height % skip;

		final int offset = UtilDownConvolve.computeOffset(skip,radius);
		final int offsetEndX = UtilDownConvolve.computeMaxSide(input.width,skip,radius)+skip;
		final int offsetEndY = UtilDownConvolve.computeMaxSide(input.height,skip,radius)+skip;

		// convolve across the left and right borders
		for (int y = 0; y < height; y += skip) {

			int minI = y >= radius ? -radius : -y;
			int maxI = input.height-y-1;
			if( maxI > radius ) maxI = radius;

			int indexDst = output.startIndex + (y/skip)* output.stride;

			for( int x = 0; x < offset; x += skip ) {

				float total = 0;
				float weight = 0;

				for( int i = minI; i <= maxI; i++ ) {
					int indexSrc = input.startIndex + (y+i)* input.stride+x;
					int indexKer = (i+radius)*kernelWidth;

					for( int j = -x; j <= radius; j++ ) {
						float w = dataKer[indexKer+j+radius];
						weight += w;
						total += (dataSrc[indexSrc+j]) * w;
					}
				}

				dataDst[indexDst++] = (total/weight);
			}

			indexDst = output.startIndex + (y/skip)* output.stride + offsetEndX/skip;
			for( int x = offsetEndX; x < width; x += skip ) {

				int maxJ = input.width-x-1;
				if( maxJ > radius ) maxJ = radius;

				float total = 0;
				float weight = 0;

				for( int i = minI; i <= maxI; i++ ) {
					int indexSrc = input.startIndex + (y+i)*input.stride + x;
					int indexKer = (i+radius)*kernelWidth;

					for( int j = -radius; j <= maxJ; j++ ) {
						float w = dataKer[indexKer+j+radius];
						weight += w;
						total += (dataSrc[indexSrc+j])* w;
					}
				}

				dataDst[indexDst++] = (total/weight);
			}
		}

		// convolve across the top border while avoiding convolving the corners again
		for (int y = 0; y < radius; y += skip) {

			int indexDst = output.startIndex + (y/skip)*output.stride + offset/skip;

			for( int x = offset; x < offsetEndX; x += skip ) {

				float total = 0;
				float weight = 0;

				for( int i = -y; i <= radius; i++ ) {
					int indexSrc = input.startIndex + (y+i)* input.stride+x;
					int indexKer = (i+radius)*kernelWidth;

					for( int j = -radius; j <= radius; j++ ) {
						float w = dataKer[indexKer+j+radius];
						weight += w;
						total += (dataSrc[indexSrc + j])* w;
					}
				}

				dataDst[indexDst++] = (total/weight);
			}
		}

		// convolve across the bottom border
		for (int y = offsetEndY; y < height; y += skip) {

			int maxI = input.height - y - 1;
			if( maxI > radius ) maxI = radius;

			int indexDst = output.startIndex + (y/skip)*output.stride + offset/skip;

			for( int x = offset; x < offsetEndX; x += skip ) {

				float total = 0;
				float weight = 0;

				for( int i = -radius; i <= maxI; i++ ) {
					int indexSrc = input.startIndex + (y+i)* input.stride+x;
					int indexKer = (i+radius)*kernelWidth;

					for( int j = -radius; j <= radius; j++ ) {
						float w = dataKer[indexKer+j+radius];
						weight += w;
						total += (dataSrc[indexSrc + j])* w;
					}
				}

				dataDst[indexDst++] = (total/weight);
			}
		}
	}

	public static void horizontal(Kernel1D_I32 kernel, ImageUInt8 input, ImageInt8 output , int skip ) {
		final byte[] dataSrc = input.data;
		final byte[] dataDst = output.data;
		final int[] dataKer = kernel.data;

		final int radius = kernel.getRadius();
		final int offset = UtilDownConvolve.computeOffset(skip,radius);
		final int offsetEnd = UtilDownConvolve.computeMaxSide(input.width,skip,radius)+skip;

		final int width = input.width - input.width % skip;
		final int height = input.getHeight();

		for (int y = 0; y < height; y++) {

			int indexDest = output.startIndex + y*output.stride;

			for( int x = 0; x < offset; x += skip ) {
				int indexSrc = input.startIndex + y*input.stride+x;
				int total = 0;
				int weight = 0;

				for( int k = -x; k <= radius; k++ ) {
					int w = dataKer[k+radius];
					weight += w;
					total += (dataSrc[indexSrc+k] & 0xFF) * w;
				}
				dataDst[indexDest++] = (byte)((total+weight/2)/weight);
			}

			indexDest = output.startIndex + y*output.stride + offsetEnd/skip;

			for( int x = offsetEnd; x < width; x += skip ) {
				int indexSrc = input.startIndex + y*input.stride+x;
				int total = 0;
				int weight = 0;

				int endKernel = input.width-x-1;
				if( endKernel > radius ) endKernel = radius;

				for( int k = -radius; k <= endKernel; k++ ) {
					int w = dataKer[k+radius];
					weight += w;
					total += (dataSrc[indexSrc+k] & 0xFF) * w;
				}
				dataDst[indexDest++] = (byte)((total+weight/2)/weight);
			}
		}
	}

	public static void vertical(Kernel1D_I32 kernel, ImageUInt8 input, ImageInt8 output , int skip ) {
		final byte[] dataSrc = input.data;
		final byte[] dataDst = output.data;
		final int[] dataKer = kernel.data;

		final int radius = kernel.getRadius();
		final int offset = UtilDownConvolve.computeOffset(skip,radius);
		final int offsetEnd = UtilDownConvolve.computeMaxSide(input.height,skip,radius)+skip;

		final int width = input.width;
		final int height = input.height - input.height % skip;

		for( int y = 0; y < offset; y += skip ) {
			int indexDest = output.startIndex + (y/skip)*output.stride;

			for( int x = 0; x < width; x++ ) {
				int indexSrc = input.startIndex + y*input.stride+x;
				int total = 0;
				int weight = 0;

				for( int k = -y; k <= radius; k++ ) {
					int w = dataKer[k+radius];
					weight += w;
					total += (dataSrc[indexSrc+k*input.stride] & 0xFF) * w;
				}
				dataDst[indexDest++] = (byte)((total+weight/2)/weight);
			}
		}

		for( int y = offsetEnd; y < height; y += skip ) {
			int indexDest = output.startIndex + (y/skip)*output.stride;
			int endKernel = input.height-y-1;
			if( endKernel > radius ) endKernel = radius;

			for( int x = 0; x < width; x++ ) {
				int indexSrc = input.startIndex + y*input.stride+x;
				int total = 0;
				int weight = 0;

				for( int k = -radius; k <= endKernel; k++ ) {
					int w = dataKer[k+radius];
					weight += w;
					total += (dataSrc[indexSrc+k*input.stride] & 0xFF) * w;
				}
				dataDst[indexDest++] = (byte)((total+weight/2)/weight);
			}
		}
	}

	public static void convolve(Kernel2D_I32 kernel, ImageUInt8 input, ImageInt8 output , int skip ) {
		final byte[] dataSrc = input.data;
		final byte[] dataDst = output.data;
		final int[] dataKer = kernel.data;

		final int radius = kernel.getRadius();
		final int kernelWidth = kernel.getWidth();

		final int width = input.width - input.width % skip;
		final int height = input.height - input.height % skip;

		final int offset = UtilDownConvolve.computeOffset(skip,radius);
		final int offsetEndX = UtilDownConvolve.computeMaxSide(input.width,skip,radius)+skip;
		final int offsetEndY = UtilDownConvolve.computeMaxSide(input.height,skip,radius)+skip;

		// convolve across the left and right borders
		for (int y = 0; y < height; y += skip) {

			int minI = y >= radius ? -radius : -y;
			int maxI = input.height-y-1;
			if( maxI > radius ) maxI = radius;

			int indexDst = output.startIndex + (y/skip)* output.stride;

			for( int x = 0; x < offset; x += skip ) {

				int total = 0;
				int weight = 0;

				for( int i = minI; i <= maxI; i++ ) {
					int indexSrc = input.startIndex + (y+i)* input.stride+x;
					int indexKer = (i+radius)*kernelWidth;

					for( int j = -x; j <= radius; j++ ) {
						int w = dataKer[indexKer+j+radius];
						weight += w;
						total += (dataSrc[indexSrc+j] & 0xFF) * w;
					}
				}

				dataDst[indexDst++] = (byte)((total+weight/2)/weight);
			}

			indexDst = output.startIndex + (y/skip)* output.stride + offsetEndX/skip;
			for( int x = offsetEndX; x < width; x += skip ) {

				int maxJ = input.width-x-1;
				if( maxJ > radius ) maxJ = radius;

				int total = 0;
				int weight = 0;

				for( int i = minI; i <= maxI; i++ ) {
					int indexSrc = input.startIndex + (y+i)*input.stride + x;
					int indexKer = (i+radius)*kernelWidth;

					for( int j = -radius; j <= maxJ; j++ ) {
						int w = dataKer[indexKer+j+radius];
						weight += w;
						total += (dataSrc[indexSrc+j] & 0xFF)* w;
					}
				}

				dataDst[indexDst++] = (byte)((total+weight/2)/weight);
			}
		}

		// convolve across the top border while avoiding convolving the corners again
		for (int y = 0; y < radius; y += skip) {

			int indexDst = output.startIndex + (y/skip)*output.stride + offset/skip;

			for( int x = offset; x < offsetEndX; x += skip ) {

				int total = 0;
				int weight = 0;

				for( int i = -y; i <= radius; i++ ) {
					int indexSrc = input.startIndex + (y+i)* input.stride+x;
					int indexKer = (i+radius)*kernelWidth;

					for( int j = -radius; j <= radius; j++ ) {
						int w = dataKer[indexKer+j+radius];
						weight += w;
						total += (dataSrc[indexSrc + j] & 0xFF)* w;
					}
				}

				dataDst[indexDst++] = (byte)((total+weight/2)/weight);
			}
		}

		// convolve across the bottom border
		for (int y = offsetEndY; y < height; y += skip) {

			int maxI = input.height - y - 1;
			if( maxI > radius ) maxI = radius;

			int indexDst = output.startIndex + (y/skip)*output.stride + offset/skip;

			for( int x = offset; x < offsetEndX; x += skip ) {

				int total = 0;
				int weight = 0;

				for( int i = -radius; i <= maxI; i++ ) {
					int indexSrc = input.startIndex + (y+i)* input.stride+x;
					int indexKer = (i+radius)*kernelWidth;

					for( int j = -radius; j <= radius; j++ ) {
						int w = dataKer[indexKer+j+radius];
						weight += w;
						total += (dataSrc[indexSrc + j] & 0xFF)* w;
					}
				}

				dataDst[indexDst++] = (byte)((total+weight/2)/weight);
			}
		}
	}

	public static void horizontal(Kernel1D_I32 kernel, ImageSInt16 input, ImageInt16 output , int skip ) {
		final short[] dataSrc = input.data;
		final short[] dataDst = output.data;
		final int[] dataKer = kernel.data;

		final int radius = kernel.getRadius();
		final int offset = UtilDownConvolve.computeOffset(skip,radius);
		final int offsetEnd = UtilDownConvolve.computeMaxSide(input.width,skip,radius)+skip;

		final int width = input.width - input.width % skip;
		final int height = input.getHeight();

		for (int y = 0; y < height; y++) {

			int indexDest = output.startIndex + y*output.stride;

			for( int x = 0; x < offset; x += skip ) {
				int indexSrc = input.startIndex + y*input.stride+x;
				int total = 0;
				int weight = 0;

				for( int k = -x; k <= radius; k++ ) {
					int w = dataKer[k+radius];
					weight += w;
					total += (dataSrc[indexSrc+k]) * w;
				}
				dataDst[indexDest++] = (short)((total+weight/2)/weight);
			}

			indexDest = output.startIndex + y*output.stride + offsetEnd/skip;

			for( int x = offsetEnd; x < width; x += skip ) {
				int indexSrc = input.startIndex + y*input.stride+x;
				int total = 0;
				int weight = 0;

				int endKernel = input.width-x-1;
				if( endKernel > radius ) endKernel = radius;

				for( int k = -radius; k <= endKernel; k++ ) {
					int w = dataKer[k+radius];
					weight += w;
					total += (dataSrc[indexSrc+k]) * w;
				}
				dataDst[indexDest++] = (short)((total+weight/2)/weight);
			}
		}
	}

	public static void vertical(Kernel1D_I32 kernel, ImageSInt16 input, ImageInt16 output , int skip ) {
		final short[] dataSrc = input.data;
		final short[] dataDst = output.data;
		final int[] dataKer = kernel.data;

		final int radius = kernel.getRadius();
		final int offset = UtilDownConvolve.computeOffset(skip,radius);
		final int offsetEnd = UtilDownConvolve.computeMaxSide(input.height,skip,radius)+skip;

		final int width = input.width;
		final int height = input.height - input.height % skip;

		for( int y = 0; y < offset; y += skip ) {
			int indexDest = output.startIndex + (y/skip)*output.stride;

			for( int x = 0; x < width; x++ ) {
				int indexSrc = input.startIndex + y*input.stride+x;
				int total = 0;
				int weight = 0;

				for( int k = -y; k <= radius; k++ ) {
					int w = dataKer[k+radius];
					weight += w;
					total += (dataSrc[indexSrc+k*input.stride]) * w;
				}
				dataDst[indexDest++] = (short)((total+weight/2)/weight);
			}
		}

		for( int y = offsetEnd; y < height; y += skip ) {
			int indexDest = output.startIndex + (y/skip)*output.stride;
			int endKernel = input.height-y-1;
			if( endKernel > radius ) endKernel = radius;

			for( int x = 0; x < width; x++ ) {
				int indexSrc = input.startIndex + y*input.stride+x;
				int total = 0;
				int weight = 0;

				for( int k = -radius; k <= endKernel; k++ ) {
					int w = dataKer[k+radius];
					weight += w;
					total += (dataSrc[indexSrc+k*input.stride]) * w;
				}
				dataDst[indexDest++] = (short)((total+weight/2)/weight);
			}
		}
	}

	public static void convolve(Kernel2D_I32 kernel, ImageSInt16 input, ImageInt16 output , int skip ) {
		final short[] dataSrc = input.data;
		final short[] dataDst = output.data;
		final int[] dataKer = kernel.data;

		final int radius = kernel.getRadius();
		final int kernelWidth = kernel.getWidth();

		final int width = input.width - input.width % skip;
		final int height = input.height - input.height % skip;

		final int offset = UtilDownConvolve.computeOffset(skip,radius);
		final int offsetEndX = UtilDownConvolve.computeMaxSide(input.width,skip,radius)+skip;
		final int offsetEndY = UtilDownConvolve.computeMaxSide(input.height,skip,radius)+skip;

		// convolve across the left and right borders
		for (int y = 0; y < height; y += skip) {

			int minI = y >= radius ? -radius : -y;
			int maxI = input.height-y-1;
			if( maxI > radius ) maxI = radius;

			int indexDst = output.startIndex + (y/skip)* output.stride;

			for( int x = 0; x < offset; x += skip ) {

				int total = 0;
				int weight = 0;

				for( int i = minI; i <= maxI; i++ ) {
					int indexSrc = input.startIndex + (y+i)* input.stride+x;
					int indexKer = (i+radius)*kernelWidth;

					for( int j = -x; j <= radius; j++ ) {
						int w = dataKer[indexKer+j+radius];
						weight += w;
						total += (dataSrc[indexSrc+j]) * w;
					}
				}

				dataDst[indexDst++] = (short)((total+weight/2)/weight);
			}

			indexDst = output.startIndex + (y/skip)* output.stride + offsetEndX/skip;
			for( int x = offsetEndX; x < width; x += skip ) {

				int maxJ = input.width-x-1;
				if( maxJ > radius ) maxJ = radius;

				int total = 0;
				int weight = 0;

				for( int i = minI; i <= maxI; i++ ) {
					int indexSrc = input.startIndex + (y+i)*input.stride + x;
					int indexKer = (i+radius)*kernelWidth;

					for( int j = -radius; j <= maxJ; j++ ) {
						int w = dataKer[indexKer+j+radius];
						weight += w;
						total += (dataSrc[indexSrc+j])* w;
					}
				}

				dataDst[indexDst++] = (short)((total+weight/2)/weight);
			}
		}

		// convolve across the top border while avoiding convolving the corners again
		for (int y = 0; y < radius; y += skip) {

			int indexDst = output.startIndex + (y/skip)*output.stride + offset/skip;

			for( int x = offset; x < offsetEndX; x += skip ) {

				int total = 0;
				int weight = 0;

				for( int i = -y; i <= radius; i++ ) {
					int indexSrc = input.startIndex + (y+i)* input.stride+x;
					int indexKer = (i+radius)*kernelWidth;

					for( int j = -radius; j <= radius; j++ ) {
						int w = dataKer[indexKer+j+radius];
						weight += w;
						total += (dataSrc[indexSrc + j])* w;
					}
				}

				dataDst[indexDst++] = (short)((total+weight/2)/weight);
			}
		}

		// convolve across the bottom border
		for (int y = offsetEndY; y < height; y += skip) {

			int maxI = input.height - y - 1;
			if( maxI > radius ) maxI = radius;

			int indexDst = output.startIndex + (y/skip)*output.stride + offset/skip;

			for( int x = offset; x < offsetEndX; x += skip ) {

				int total = 0;
				int weight = 0;

				for( int i = -radius; i <= maxI; i++ ) {
					int indexSrc = input.startIndex + (y+i)* input.stride+x;
					int indexKer = (i+radius)*kernelWidth;

					for( int j = -radius; j <= radius; j++ ) {
						int w = dataKer[indexKer+j+radius];
						weight += w;
						total += (dataSrc[indexSrc + j])* w;
					}
				}

				dataDst[indexDst++] = (short)((total+weight/2)/weight);
			}
		}
	}

}
